/*
 $Id$

 Copyright (C) 2006-2007 by David Cotton

 This program is free software; you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software
 Foundation; either version 2 of the License, or (at your option) any later
 version.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
 Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package fr.free.jchecs.ai;

import static fr.free.jchecs.core.Constants.APPLICATION_NAME;
import static fr.free.jchecs.core.Constants.APPLICATION_VERSION;
import static fr.free.jchecs.core.PieceType.PAWN;
import static fr.free.jchecs.core.SANUtils.toMove;
import static fr.free.jchecs.core.SANUtils.toSAN;
import static fr.free.jchecs.core.board.BoardFactory.State.EMPTY;
import static fr.free.jchecs.core.board.BoardFactory.State.STARTING;
import static fr.free.jchecs.core.board.BoardFactory.Type.FASTEST;
import static fr.free.jchecs.core.board.FENUtils.toBoard;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Logger;

import fr.free.jchecs.core.Game;
import fr.free.jchecs.core.SANException;
import fr.free.jchecs.core.Square;
import fr.free.jchecs.core.Game.State;
import fr.free.jchecs.core.board.Board;
import fr.free.jchecs.core.board.BoardFactory;
import fr.free.jchecs.core.board.FENException;
import fr.free.jchecs.core.move.Move;
import fr.free.jchecs.core.move.MoveGenerator;

/**
 * Utility class providing the interface between the AI and I.H.M. using the
 * protocol XBoard / WinBoard.
 * 
 * @author David Cotton
 */
public final class XBoardAdapter {
	/** String identifying the application. */
	private static final String APPLICATION_STRING = APPLICATION_NAME + " "
			+ APPLICATION_VERSION;

	/** String listing the capabilities of the engine. */
	private static final String FEATURES_STRING = "feature analyze=0 colors=0 myname=\""
			+ APPLICATION_STRING
			+ "\" pause=0 ping=1 playother=0 san=1 setboard=1 sigint=0 sigterm=0 "
			+ "time=0 usermove=1 variants=\"normal\" done=1";

	/** Log class. */
	private static final Logger LOGGER = Logger.getLogger(XBoardAdapter.class
			.getName());

	/** A.I. engine used. */
	private static final Engine ENGINE = EngineFactory.newInstance();

	/** State of the game. */
	private static Game S_game = new Game();

	/** State mode "force". */
	private static boolean S_forceMode;

	/** State game mode low / high. */
	private static boolean S_hardMode;

	/** Position indicator unlawful. */
	private static boolean S_illegalPosition;

	/**
	 * Utility class: do not instantiate.
	 */
	private XBoardAdapter() {
		// Rien de particulier...
	}

	/**
	 * Starts the interface.
	 * 
	 * @param pArgs
	 *            Arguments to the command line, ignored.
	 */
	public static void main(final String[] pArgs) {
		assert pArgs != null;

		final BufferedReader in = new BufferedReader(new InputStreamReader(
				System.in));

		while (true) {
			String commande = null;
			try {
				commande = in.readLine();
			} catch (final IOException e) {
				LOGGER.severe(e.toString());
			}
			if (commande == null) {
				LOGGER.severe("Communication error.");
				System.exit(-1);
			} else {
				parseCommand(commande.trim());
			}
		}
	}

	/**
	 * Interprets the commands received.
	 * 
	 * @param pCommande
	 *            Order received.
	 */
	private static void parseCommand(final String pCommande) {
		assert pCommande != null;

		if (pCommande.startsWith("accepted ")) {
			// Tant mieux, mais il n'y a rien à faire.
		} else if (pCommande.startsWith("level ")) {
			// Non impléméntée...
		} else if (pCommande.startsWith("ping ")) {
			System.out.println("pong" + pCommande.substring(4));
		} else if (pCommande.startsWith("protover ")) {
			System.out.println(FEATURES_STRING);
		} else if (pCommande.startsWith("rejected ")) {
			System.out.println("tellusererror Missing feature "
					+ pCommande.substring(9));
		} else if (pCommande.startsWith("result ")) {
			// Non impléméntée...
		} else if (pCommande.startsWith("setboard ")) {
			final String fen = pCommande.substring(9);

			Board etat = null;
			try {
				etat = toBoard(fen);
			} catch (final FENException e) {
				// Géré par la suite...
			}
			if (etat == null) {
				S_illegalPosition = true;
				System.out.println("tellusererror Illegal position");
			} else {
				S_illegalPosition = false;
				S_game.resetTo(BoardFactory.valueOf(FASTEST, EMPTY)
						.derive(etat));
			}
		} else if (pCommande.startsWith("usermove ")) {
			final String xbSAN = pCommande.substring(9);
			// Filtre les '=' que certains programmes peuvent en cas de
			// promotion de pion...
			// ... et transforme les "o" majuscules utilisés par XBoard pour les
			// roques en zéros.
			final String san = xbSAN.replace("=", "").replace('O', '0');
			Move mvt = null;
			try {
				mvt = toMove(S_game.getBoard(), san);
			} catch (final SANException e) {
				// géré par la suite...
			}
			if ((mvt == null) || S_illegalPosition) {
				System.out.println("Illegal move: " + xbSAN);
			} else {
				S_game.moveFromCurrent(mvt);
				if (!S_forceMode) {
					think();
				}
			}
		} else if (pCommande.equals("computer")) {
			// Non impléméntée...
		} else if (pCommande.equals("easy")) {
			S_hardMode = false;
		} else if (pCommande.equals("force")) {
			S_forceMode = true;
		} else if (pCommande.equals("go")) {
			if (S_illegalPosition) {
				System.out.println("Error (illegal position): go");
			} else {
				S_forceMode = false;
				think();
			}
		} else if (pCommande.equals("hard")) {
			S_hardMode = true;
		} else if (pCommande.equals("new")) {
			S_game.resetTo(BoardFactory.valueOf(FASTEST, STARTING));
			S_forceMode = false;
			S_illegalPosition = false;
		} else if (pCommande.equals("nopost")) {
			// Non impléméntée...
		} else if (pCommande.equals("post")) {
			// Non impléméntée...
		} else if (pCommande.equals("quit")) {
			System.exit(0);
		} else if (pCommande.equals("random")) {
			// Rien à faire.
		} else if (pCommande.equals("xboard")) {
			System.out.println(APPLICATION_STRING + " started in xboard mode.");
		} else {
			System.out.println("Error (unknown command): " + pCommande);
		}
	}

	/**
	 * Tests the end game.
	 * 
	 * @param pAbandon
	 *            Set to true if you want to test the abandonment.
	 * @return True if the game is not over.
	 */
	private static boolean testResult(final boolean pAbandon) {
		String rep = null;

		final State etat = S_game.getState();
		switch (etat) {
		case BLACK_MATES:
			rep = "0-1 {Black mates}";
			break;
		case DRAWN_BY_50_MOVE_RULE:
			if (pAbandon) {
				// Do not look abandoned if it has not a sufficient advantage (one pawn) ...
				final MoveGenerator dispo = S_game.getBoard();
				if (ENGINE.getHeuristic()
						.evaluate(dispo, dispo.isWhiteActive()) < PAWN
						.getValue()) {
					rep = "1/2-1/2 {Drawn by 50 moves rule}";
				}
			}
			break;
		case DRAWN_BY_TRIPLE_REPETITION:
			if (pAbandon) {
				// Do not look abandoned if one is in trouble (unpion late) ...
				final MoveGenerator dispo = S_game.getBoard();
				if (ENGINE.getHeuristic()
						.evaluate(dispo, dispo.isWhiteActive()) < -PAWN
						.getValue()) {
					rep = "1/2-1/2 {Drawn by triple repetition}";
				}
			}
			break;
		case IN_PROGRESS:
			break;
		case STALEMATE:
			rep = "1/2-1/2 {Stalemate}";
			break;
		case WHITE_MATES:
			rep = "1-0 {White mates}";
			break;
		default:
			assert false;
		}

		if (rep != null) {
			System.out.println(rep);
		}

		return rep == null;
	}

	/**
	 * Search and returns the best move from the current position.
	 */
	private static void think() {
		if (testResult(true)) {
			final MoveGenerator etat = BoardFactory.valueOf(FASTEST, EMPTY)
					.derive(S_game.getBoard());

			int profondeur = 5;
			if (S_hardMode) {
				int nbPieces = 0;
				for (final Square s : Square.values()) {
					if (etat.getPieceAt(s) != null) {
						nbPieces++;
					}
				}
				if (nbPieces <= 6) {
					profondeur++;
				}
			}
			ENGINE.setSearchDepthLimit(profondeur);

			final Move mvt = ENGINE.getMoveFor(etat);

			final String san = toSAN(etat, mvt);
			S_game.moveFromCurrent(mvt);
			// Converts zeros "o" capitalized used for XBoard by roques ...
			// ... et supprime les marques de prise "en passant" inconnues de XBoard.
			final String xbSAN = san.replace(" e.p.", "").replace("O", "0");
			System.out.println("move " + xbSAN);

			testResult(false);
		}
	}
}
